from threading import local
from clutch.util import bag

def object_manager(obj):
    return getattr(obj, '_mgr', None)

def AppLocalProxy(factory):
    from clutch.globals import C
    p = Proxy()
    def app_id():
        if hasattr(C, 'G'):
            return id(C.G.application)
        else:
            return 0
    p._mgr = ContextualManager(factory=factory, contextfunc=app_id)
    return p

def StackedThreadLocalProxy(factory, initial_factory=None):
    p = Proxy()
    p._mgr = StackedThreadLocalManager(factory, initial_factory)
    return p

def ForwardingProxy(obj, name):
    p = Proxy()
    p._mgr = ForwardingManager(obj, name)
    return p

def delegate(name):
    def inner(self, *l, **kw):
        try:
            return getattr(self._mgr, name)(*l, **kw)
        except TypeError, te:
            import pdb; pdb.set_trace()
    inner.__name__ = name
    return inner

def delegate_property(name):
    def get(self):
        return self._mgr.getattr(name)
    def set(self, value):
        return self._mgr.setattr(name, value)
    return property(get,set)

class Proxy(object):

    def __new__(cls, *l):
        if hasattr(cls, '_mgr'):
            return cls._mgr.new_obj(cls, *l)
        else:
            return object.__new__(cls, *l)

    def __init__(self, *l, **kw):
        if hasattr(self, '_mgr'):
            self._mgr.init(*l, **kw)
        else:
            object.__init__(self, *l, **kw)

    def __setattr__(self, name, value):
        if name != '_mgr':
            return self._mgr.setattr(name, value)
        else:
            self.__dict__[name] = value

    __class__ = delegate_property('__class__')
    __getattr__ = delegate('getattr')
    __call__ = delegate('call')
    __repr__ = delegate('repr')
    __str__ = delegate('str')
    __eq__ = delegate('eq')
    __hash__ = delegate('hash')
    __iter__ = delegate('iter')
    __contains__ = delegate('contains')

class Manager(object):
    _saved_init = None
    
    def save_init(self, old_init):
        self._saved_init = old_init

    def call(self, *l, **kw):
        return self.obj(*l, **kw)

    def getattr(self, name):
        return getattr(self.obj, name)

    def setattr(self, name, value):
        return setattr(self.obj, name, value)

    def repr(self):
        return repr(self.obj)

    def str(self):
        return str(self.obj)

    def iter(self):
        return iter(self.obj)

    def contains(self, o):
        return o in self.obj

    def eq(self, o):
        return self.obj == o

    def hash(self):
        return hash(self.obj)

    def get_property(self, name):
        return getattr(self.obj, name)

    def set_property(self, name, value):
        setattr(self.obj, name, value)

class ContextualManager(Manager):

    def __init__(self, factory, contextfunc):
        self.factory = factory
        self.contextfunc = contextfunc
        self.registry = {}

    @property
    def obj(self):
        ctx = self.contextfunc()
        obj = self.registry.get(ctx, ())
        if obj == ():
            obj = self.registry[obj] = self.factory()
        return obj

class ThreadLocalManager(Manager):
    
    def __init__(self, factory):
        self.factory = factory
        self.tl = local()

    @property
    def obj(self):
        obj = getattr(self.tl, 'impl', ())
        if obj == ():
            obj = self.tl.impl = self.factory()
        return obj

class StackedManager(Manager):

    def __init__(self, factory, initial_factory=None):
        self.factory = factory
        if initial_factory is None:
            initial_factory = factory
        self.initial_factory = initial_factory

    def push(self, value=()):
        if value == ():
            if not self.stack:
                value = self.initial_factory()
            else:
                value = self.factory()
        self.stack.append(value)

    def pop(self):
        self.stack.pop()

    def empty(self):
        return len(self.stack) == 0

    def getattr(self, name):
        for frame in reversed(self.stack):
            value = getattr(frame, name, ())
            if value != (): return value
        raise AttributeError, name

    @property
    def obj(self):
        return self.stack[-1]

    def freeze(self):
        '''return a "frozen" copy of the object at the current stack level'''
        result = bag()
        for frame in self.stack:
            for k in dir(frame):
                result[k] = getattr(frame, k)
        return result

class StackedContextualManager(StackedManager):

    def __init__(self, factory, contextfunc):
        StackedManager.__init__(self, factory)
        self.contextfunc = contextfunc
        self.registry = defaultdict(list)

    @property
    def stack(self):
        ctx = self.contextfunc()
        return self.registry[ctx]
        
class StackedThreadLocalManager(StackedManager):

    def __init__(self, *l):
        StackedManager.__init__(self, *l)
        self.tl = local()

    @property
    def stack(self):
        s = getattr(self.tl, 'stack', ())
        if s == ():
            s = self.tl.stack = []
        return s

    def __repr__(self):
        l = '\n'.join(map(repr, self.stack))
        return '<StackedThreadLocalManager\n    %s>' % (
            l.replace('\n', '\n    '))

class ForwardingManager(Manager):

    def __init__(self, obj, name):
        self._obj = obj
        self._name = name

    @property
    def obj(self):
        return getattr(self._obj, self._name)

    def freeze(self):
        '''return a "frozen" copy of the object at the current stack level'''
        result = bag()
        obj = self.obj
        for k in dir(obj):
            result[k] = getattr(obj, k)
        return result

##class Proxy(object):

##    def __getattr__(self, name):
##        return getattr(self.get(), name)

##class ContextualProxy(Proxy):

##    def __init__(self, create=None, contextfunc=None):
##        self._registry = {}
##        if create is not None:
##            self._create = create
##        if contextfunc is not None:
##            self._contextfunc = contextfunc

##    def get(self):
##        key = self._contextfunc()
##        try:
##            impl = self._registry[key]
##        except KeyError, ke:
##            impl = self._registry[key] = self._create()
##        return impl
        
##class ThreadLocalProxy(Proxy):

##    def __init__(self, create=None):
##        self._tl = local()
##        if create is not None:
##            self._create = create

##    def get(self):
##        if hasattr(self._tl, 'impl'):
##            impl = self._tl.impl
##        else:
##            impl = self._tl.impl = self._create()
##        return impl

##    def clear(self):
##        if hasattr(self._tl, 'impl'):
##            del self._tl.impl

##class ForwardingProxy(Proxy):

##    def __init__(self, obj, name):
##        self._obj = obj
##        self._name = name

##    def get(self):
##        return getattr(self._obj, self._name)

##class AppLocalProxy(ContextualProxy):

##    def __init__(self, create=None):
##        ContextualProxy.__init__(self, create, self._context)

##    def _context(self):
##        from clutch.globals import C
##        return id(C.G)
